{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit Screens;

interface

uses Classes, Graphics, UIObjects, Actions, Containers, BaseControls,
     MetaValues, XMLHelper;

{$IFDEF DESIGNER}
const
  MODIFY_ACTIONS    = 'Edit Actions';
  MODIFY_CONTENTS = 'Edit Contents';
  MODIFY_METADATA   = 'Edit Metadata';

  // Property categories and hints for designer
  CAT_LAYOUT = 'Screen Layout';
  HINT_ACTIONS = 'defines action handling for this screen';
  HINT_METAINFO = 'defines meta info values for this screen';
  HINT_CONTENTS = 'specifies a list of containers and controls to display in the screen';
  HINT_DEFAULT = 'specifies a control to receive initial focus';
{$ENDIF}

type
  TAHMScreenActions = (saInit, saShow, saHide, saFocus, saUnfocus);

  TAHMScreenLayout = class(TAHMUIObject)
  private
    FBusy: Boolean;
    FVisible: Boolean;
    FFocussed: Boolean;
    FMouseDown: Boolean;
    FActionEvents: set of TAHMScreenActions;
    FActions: TAHMActionList;
    FContents: TAHMAbstractControlList;
    FCurControls: TAHMControlList;
    FNavControls: TAHMControlList;
    FMetaValues: TAHMMetaValueList;
    FMouseControl: TAHMInteractiveControl;
    FFocusControl: TAHMInteractiveControl;
    FDefaultControl: TAHMInteractiveControl;
    FSaveControl: TAHMInteractiveControl;
  protected
    procedure SetVisible(Visible: Boolean);
    procedure SetFocussed(Focussed: Boolean);
    procedure SetFocusControl(Control: TAHMInteractiveControl);
    procedure ClearProperties; override;
  public
    constructor Create(const Name: String); override;
    destructor Destroy; override;
    property CurrentControls: TAHMControlList read FCurControls;
    property NavigableControls: TAHMControlList read FNavControls;
    property FocusControl: TAHMInteractiveControl read FFocusControl write SetFocusControl;
    property SaveControl: TAHMInteractiveControl read FSaveControl;
    property Visible: Boolean read FVisible write SetVisible;
    property Focussed: Boolean read FFocussed write SetFocussed;
    procedure Initialise;
    procedure UpdateActions;
    procedure UpdateMetaValues;
    procedure SetDefaultControl(const Name: String);
    procedure SetDefault(Control: TAHMInteractiveControl);
    procedure RefreshCurrentControls;
    procedure RefreshNavigableControls;
    function PreviewMouseDown(X, Y: Single): Boolean; override;
    function PreviewMouseMove(X, Y: Single; Down: Boolean): Boolean; override;
    function PreviewMouseUp(X, Y: Single): Boolean; override;
    function PreviewMouseWheel(Delta: Integer): Boolean; override;
    function RenderPreview: Boolean; override;
    function ControlAtCoordinate(X, Y: Single): TAHMInteractiveControl;
    function RemoveAbstractControl(Remove: TAHMAbstractControl): TAHMUIObject;
    function FindAbstractControl(X, Y: Single; Ignore: TAHMAbstractControl): TAHMAbstractControl;
    function FindControl(X, Y: Single; Ignore: TAHMControl): TAHMControl;
    function FindContainer(X, Y: Single; Ignore: TAHMContainer): TAHMContainer;
    function ModifyActions: Integer; override;
    function Modify(Action: Integer): Boolean; override;
    function ModifyCaption(Action: Integer): String; override;
    procedure MouseDown(X, Y: Single);
    procedure MouseMove(X, Y: Single; Down: Boolean);
    procedure MouseUp(X, Y: Single);
    procedure MouseWheel(Delta: Integer);
    procedure InvalidateUIObject(UIObject: TAHMUIObject); override;
    procedure Render;
    procedure Assign(Source: TPersistent); override;
    procedure AssignContents(Source: TAHMUIObject); override;
    procedure SaveXMLBody(Lines: TStrings); override;
    procedure ParseFromXML(XML: TAHMXMLHelper); override;
    procedure DeleteUIObject(UIObject: TAHMUIObject); override;
    procedure BuildProperties(Properties: TAHMUIPropertyList); override;
  published
    property Actions: TAHMActionList read FActions;
    property MetaValues: TAHMMetaValueList read FMetaValues;
    property Contents: TAHMAbstractControlList read FContents;
    property DefaultControl: TAHMInteractiveControl read FDefaultControl write SetDefault;
  end;

  TAHMScreenLayoutList = class(TAHMUIObjectList)
  private
  protected
  public
    constructor Create; override;
    function AddScreenLayout(const InheritFrom, NewName: String): TAHMScreenLayout;
    function GetScreenLayout(const Name: String): TAHMScreenLayout;
    function GetDefaultLayout: TAHMScreenLayout;
  end;


implementation

uses SysUtils, ApplicationManager, TagNames, XMLTags, MetaConsts, Screen,
     {$IFDEF DESIGNER}DesignerList,{$ENDIF} Logging, ErrorCodes, AHMTypes;

constructor TAHMScreenLayoutList.Create;
begin
  inherited Create;

  XMLTagName := TAG_SCREENS;
  ChildClass := TAHMScreenLayout;
  DefaultSort := lsNatural;
end;

function TAHMScreenLayoutList.AddScreenLayout(const InheritFrom, NewName: String): TAHMScreenLayout;
begin
  Result := TAHMScreenLayout(Inherit(InheritFrom, NewName));
end;

function TAHMScreenLayoutList.GetScreenLayout(const Name: String): TAHMScreenLayout;
begin
  Result := TAHMScreenLayout(GetObject(Name));
end;

function TAHMScreenLayoutList.GetDefaultLayout: TAHMScreenLayout;
begin
  if Count > 0 then Result := TAHMScreenLayout(Objects[0])
               else Result := nil;
  if (Result = nil) and (BaseList <> nil) then
    Result := TAHMScreenLayoutList(BaseList).GetDefaultLayout;
end;

//----------------------------------------------------------------------------//

constructor TAHMScreenLayout.Create(const Name: String);
begin
  inherited Create(Name);

  XMLTagName := TAG_SCREEN;
  ObjectType := otScreens;
  ImageIndex := Ord(otScreens);
  IsPreviewable := True;

  FContents := TAHMAbstractControlList.Create;
  FMetaValues := TAHMMetaValueList.Create;
  FCurControls := TAHMControlList.Create;
  FNavControls := TAHMControlList.Create;
  FActions := TAHMActionList.Create;
  FActions.Owner := Self;

  // We want to call our init actions only once when we are first displayed
  FActionEvents := [saInit];
end;

destructor TAHMScreenLayout.Destroy;
begin
  FActions.FreeWithContents;
  FMetaValues.FreeWithContents;
  FContents.Free;
  FCurControls.Free;
  FNavControls.Free;

  inherited Destroy;
end;

procedure TAHMScreenLayout.Initialise;
begin
  // Ensure we have a current list of controls
  RefreshCurrentControls;

  // Clear mouse control and associated properties
  FMouseControl := nil;
  FMouseDown := False;

  if LogDebugEnabled then
    LogDebug('Initialised screen ' + Name);
end;

procedure TAHMScreenLayout.UpdateActions;
begin
  // Here we just fire any appropriate actions that we have flagged earlier
  if saInit in FActionEvents then
   FActions.InvokeAction(AC_INIT);
  if saShow in FActionEvents then
   FActions.InvokeAction(AC_SHOW);
  if saHide in FActionEvents then
   FActions.InvokeAction(AC_HIDE);
  if saFocus in FActionEvents then
   FActions.InvokeAction(AC_FOCUS);
  if saUnFocus in FActionEvents then
   FActions.InvokeAction(AC_UNFOCUS);

  // Clear our pending actions
  FActionEvents := [];
end;

procedure TAHMScreenLayout.UpdateMetaValues;
begin
  // Update meta values related to this screen
  if Assigned(FFocusControl) then
  begin
    // Set application hint & current control metadata for control
    ApplicationManagerInstance.MetaDataManager.SetMetaValue(META_APPHINT, FFocusControl.HintText);
    ApplicationManagerInstance.MetaDataManager.SetMetaValue(META_APPCONTROL, FFocusControl.Name);
  end
  else
  begin
    // Clear application hint & current control metadata for control
    ApplicationManagerInstance.MetaDataManager.SetMetaValue(META_APPHINT, '');
    ApplicationManagerInstance.MetaDataManager.SetMetaValue(META_APPCONTROL, '');
  end;
end;

procedure TAHMScreenLayout.SetVisible(Visible: Boolean);
begin
  // If we're changing visibility then we need to flag an action to fire
  if Visible <> FVisible then
  begin
    FVisible := Visible;
    if Visible then Include(FActionEvents, saShow)
               else Include(FActionEvents, saHide);
  end;
end;

procedure TAHMScreenLayout.SetFocussed(Focussed: Boolean);
begin
  // If we're changing focus then we need to flag an action to fire
  if Focussed <> FFocussed then
  begin
    FFocussed := Focussed;
    if Focussed then Include(FActionEvents, saFocus)
                else Include(FActionEvents, saUnFocus);
  end;
end;

procedure TAHMScreenLayout.SetFocusControl(Control: TAHMInteractiveControl);
begin
  // Do nothing if already attempting to set focus
  if FBusy then Exit;
  FBusy := True;
  try
    // Check for change in focus control
    if FFocusControl <> Control then
    begin
      // Fire unfocus actions for current control
      if FFocusControl <> nil then
      begin
        FFocusControl.Focused := False;
        FFocusControl.Actions.InvokeAction(AC_UNFOCUS);

        // Refresh list of navigable controls
        RefreshNavigableControls;
      end;

      if Control <> nil then
      begin
        // Check passed control is navigable
        if FNavControls.Contains(Control) then
        begin
          // Fire focus actions for new control
          FFocusControl := Control;
          FFocusControl.Focused := True;
          FFocusControl.Actions.InvokeAction(AC_FOCUS);
        end
        else
          // Passed control cannot receive focus
          FFocusControl := nil;
      end
      else
        // We were passed a nil control
        FFocusControl := nil;

      // Remember the control we're now pointing at so we can restore state later
      FSaveControl := FFocusControl;
    end;
  finally
    FBusy := False;
  end;
end;

procedure TAHMScreenLayout.ClearProperties;
begin
  inherited;

  // Clear custom screen layout properties
  FContents.Clear;
  FActions.ClearObjects;
  FActions.BaseList := nil;
  FMetaValues.ClearObjects;
  FDefaultControl := nil;
end;

procedure TAHMScreenLayout.SetDefaultControl(const Name: String);
var
  oControl: TAHMControl;
begin
  // Refresh our list of available controls
  RefreshCurrentControls;

  oControl := FCurControls.GetControl(Name);
  if oControl is TAHMInteractiveControl then
    FDefaultControl := TAHMInteractiveControl(oControl)
  else
    FDefaultControl := nil;

  // Store default control as our save control to restore state
  FSaveControl := FDefaultControl;
end;

procedure TAHMScreenLayout.SetDefault(Control: TAHMInteractiveControl);
begin
  if Assigned(Control) then
    SetDefaultControl(Control.Name);
end;

procedure TAHMScreenLayout.RefreshCurrentControls;
var
  i, j: Integer;
  oContent: TAHMAbstractControl;
  oContainer: TAHMContainer;
begin
  // Clear current control list contents
  FCurControls.Clear;

  // Iterate through controls and containers shown in this layout
  for i := 0 to Pred(FContents.Count) do
  begin
    oContent := TAHMAbstractControl(FContents.Objects[i]);
    if oContent is TAHMControl then
      FCurControls.Add(TAHMControl(oContent))
    else if oContent is TAHMContainer then
    begin
      // Iterate through controls shown in this container
      oContainer := TAHMContainer(oContent);
      for j := 0 to Pred(oContainer.Controls.Count) do
        FCurControls.Add(TAHMControl(oContainer.Controls.Objects[j]));
    end;
  end;

  // Now we need to refresh controls we can navigate to
  RefreshNavigableControls;
end;

procedure TAHMScreenLayout.RefreshNavigableControls;
var
  i: Integer;
  oControl: TAHMInteractiveControl;
begin
  // Clear navigable control list contents
  FNavControls.Clear;

  // Iterate though current controls looking for enabled & visible
  for i := 0 to Pred(FCurControls.Count) do
    if FCurControls.Objects[i] is TAHMInteractiveControl then
    begin
      oControl := TAHMInteractiveControl(FCurControls.Objects[i]);
      if oControl.Visible and oControl.Enabled then
        FNavControls.Add(oControl);
    end;

  // Check currently focussed control is still navigable
  if (FFocusControl = nil) or (FNavControls.IndexOf(FFocusControl.Name) = -1) then
  begin
    FFocusControl := FDefaultControl;
    FSaveControl := FDefaultControl;
  end
  else
    Exit;

  // Check default control is still navigable
  if (FFocusControl = nil) or (FNavControls.IndexOf(FFocusControl.Name) = -1) then
  begin
    FFocusControl := nil;
    FSaveControl := FDefaultControl;
  end;
end;

function TAHMScreenLayout.PreviewMouseDown(X, Y: Single): Boolean;
begin
  MouseDown(X, Y);
  Result := True;
end;

function TAHMScreenLayout.PreviewMouseMove(X, Y: Single; Down: Boolean): Boolean;
begin
  MouseMove(X, Y, Down);
  Result := True;
end;

function TAHMScreenLayout.PreviewMouseUp(X, Y: Single): Boolean;
begin
  MouseUp(X, Y);
  Result := True;
end;

function TAHMScreenLayout.PreviewMouseWheel(Delta: Integer): Boolean;
begin
  MouseWheel(Delta);
  Result := True;
end;

function TAHMScreenLayout.RenderPreview: Boolean;
begin
{$IFDEF DESIGNER}
  // Render layout to screen viewport
  with ScreenInstance.Viewport do
    Canvas.DesignerBackground(ViewportRect);
  Render;
{$ENDIF}
  Result := True;
end;

function TAHMScreenLayout.ControlAtCoordinate(X, Y: Single): TAHMInteractiveControl;
var
  i: Integer;
  Depth: TAHMDepthType;
  oContent: TAHMAbstractControl;
begin
  // Set default result - no control found
  Result := nil;

  // Iterate through contents in reverse draw order - i.e. check topmost first
  for Depth := dtForeground downto dtBackground do
    for i := Pred(FContents.Count) downto 0 do
    begin
      oContent := TAHMAbstractControl(FContents.Objects[i]);

      if oContent.Depth = Depth then
        if (oContent is TAHMInteractiveControl) and oContent.BoundsPoint(X, Y) then
        begin
          Result := TAHMInteractiveControl(oContent);
          Break;
        end
        else if oContent is TAHMContainer then
        begin
          // Check controls within this container
          Result := TAHMContainer(oContent).ControlAtCoordinate(X, Y);
          if Assigned(Result) then Break;
        end;
    end;
end;

function TAHMScreenLayout.RemoveAbstractControl(Remove: TAHMAbstractControl): TAHMUIObject;
var
  i: Integer;
  Depth: TAHMDepthType;
  oContent: TAHMAbstractControl;
begin
  // Set default result - no abstract control found
  Result := nil;

  // Iterate through contents in reverse draw order - i.e. check topmost first
  for Depth := dtForeground downto dtBackground do
    for i := Pred(FContents.Count) downto 0 do
    begin
      oContent := TAHMAbstractControl(FContents.Objects[i]);

      if oContent.Depth = Depth then
      begin
        if oContent = Remove then
        begin
          FContents.Delete(i);
          Result := Self;
          Exit;
        end
        else if oContent is TAHMContainer then
        begin
          // Use containers method for removing appropriate abstract control
          Result := TAHMContainer(oContent).RemoveAbstractControl(Remove);
          if Result <> nil then Exit;
        end;
      end;
    end;
end;

function TAHMScreenLayout.FindAbstractControl(X, Y: Single; Ignore: TAHMAbstractControl): TAHMAbstractControl;
var
  i: Integer;
  Depth: TAHMDepthType;
  oContent: TAHMAbstractControl;
begin
  // Set default result - no abstract control found
  Result := nil;

  // Iterate through contents in reverse draw order - i.e. check topmost first
  for Depth := dtForeground downto dtBackground do
    for i := Pred(FContents.Count) downto 0 do
    begin
      oContent := TAHMAbstractControl(FContents.Objects[i]);

      if oContent.Depth = Depth then
      begin
        // Skip directly owned controls until we find our ignore control
        if Ignore is TAHMControl then
        begin
          if Ignore = oContent then Ignore := nil;
          Continue;
        end;

        if (oContent is TAHMControl) and oContent.BoundsPoint(X, Y) then
        begin
          Result := oContent;
          Exit;
        end
        else if oContent is TAHMContainer then
        begin
          // Use containers method for finding appropriate abstract control
          Result := TAHMContainer(oContent).FindAbstractControl(X, Y, Ignore);
          if Result <> nil then Exit;
        end;
      end;
    end;
end;

function TAHMScreenLayout.FindControl(X, Y: Single; Ignore: TAHMControl): TAHMControl;
var
  i: Integer;
  Depth: TAHMDepthType;
  oContent: TAHMAbstractControl;
begin
  // Set default result - no control found
  Result := nil;

  // Iterate through contents in reverse draw order - i.e. check topmost first
  for Depth := dtForeground downto dtBackground do
    for i := Pred(FContents.Count) downto 0 do
    begin
      oContent := TAHMAbstractControl(FContents.Objects[i]);

      if oContent.Depth = Depth then
      begin
        // Skip directly owned controls until we find our ignore control
        if Ignore is TAHMControl then
        begin
          if Ignore = oContent then Ignore := nil;
          Continue;
        end;

        if (oContent is TAHMControl) and oContent.BoundsPoint(X, Y) then
        begin
          Result := TAHMControl(oContent);
          Exit;
        end
        else if oContent is TAHMContainer then
        begin
          // Use containers method for finding appropriate control
          Result := TAHMContainer(oContent).FindControl(X, Y, Ignore);
          if Result <> nil then Exit;
        end;
      end;
    end;
end;

function TAHMScreenLayout.FindContainer(X, Y: Single; Ignore: TAHMContainer): TAHMContainer;
var
  i: Integer;
  Depth: TAHMDepthType;
  oContent: TAHMAbstractControl;
begin
  // Set default result - no container found
  Result := nil;

  // Iterate through contents in reverse draw order - i.e. check topmost first
  for Depth := dtForeground downto dtBackground do
    for i := Pred(FContents.Count) downto 0 do
    begin
      oContent := TAHMAbstractControl(FContents.Objects[i]);

      if oContent.Depth = Depth then
      begin
        // Skip all containers until we find our ignore container
        if Ignore <> nil then
        begin
          if Ignore = oContent then Ignore := nil;
          Continue;
        end;

        // Is this a container that bounds our point?
        if (oContent is TAHMContainer) and oContent.BoundsPoint(X, Y) then
        begin
          // We found a container
          Result := TAHMContainer(oContent);
          Exit;
        end;
      end;
    end;
end;

function TAHMScreenLayout.ModifyActions: Integer;
begin
  // Screen layout supports editing actions, containers or metadata
  Result := 3;
end;

function TAHMScreenLayout.Modify(Action: Integer): Boolean;
begin
{$IFDEF DESIGNER}
  case Action of
    0: // Edit actions with list dialog
       Result := TfrmObjectList.ShowList(FActions, nil, nil);
    1: // Edit contents with list dialog
       with ApplicationManagerInstance.ThemeManager.InterfaceDescriptor do
         Result := TfrmObjectList.ShowList(FContents, ControlList, ContainerList);
    2: // Edit metadata with list dialog
       Result := TfrmObjectList.ShowList(FMetaValues, nil, nil);
    else Result := False;
  end;

  if Result then DoModified;
{$ELSE}
  Result := False;
{$ENDIF}
end;

function TAHMScreenLayout.ModifyCaption(Action: Integer): String;
begin
{$IFDEF DESIGNER}
  case Action of
    0: Result := MODIFY_ACTIONS;
    1: Result := MODIFY_CONTENTS;
    2: Result := MODIFY_METADATA;
    else Result := '';
  end;
{$ELSE}
  Result := '';
{$ENDIF}
end;

procedure TAHMScreenLayout.MouseDown(X, Y: Single);
begin
  // Record control (if any) beneath mouse when button was pressed down
  FMouseControl := ControlAtCoordinate(X, Y);
  FMouseDown := Assigned(FMouseControl);

  // Pass mouse down event to control if we found one
  if Assigned(FMouseControl) then
    FMouseControl.MouseDown(X, Y);
end;

procedure TAHMScreenLayout.MouseMove(X, Y: Single; Down: Boolean);
begin
  // If mouse button not held down then we can focus control beneath mouse
  if not FMouseDown then
  begin
    FMouseControl := ControlAtCoordinate(X, Y);
    if Assigned(FMouseControl) then SetFocusControl(FMouseControl);
  end;

  // Pass mouse move method to control to allow mouse tracking
  if Assigned(FMouseControl) then FMouseControl.MouseMove(X, Y, Down);
end;

procedure TAHMScreenLayout.MouseUp(X, Y: Single);
begin
  // Pass mouse up method to mouse control if we have one
  if Assigned(FMouseControl) then
    FMouseControl.MouseUp(X, Y)
  else
  begin
    // No mouse control selected, generate a screen & global "okay" action
    ApplicationManagerInstance.ActionManager.ProcessAction(AC_OKAY, nil);
  end;

  // Reset mouse down flag
  FMouseDown := False;
end;

procedure TAHMScreenLayout.MouseWheel(Delta: Integer);
begin
  // Pass mouse wheel method to mouse control if we have one
  if Assigned(FMouseControl) then FMouseControl.MouseWheel(Delta);
end;

procedure TAHMScreenLayout.InvalidateUIObject(UIObject: TAHMUIObject);
begin
  // Refresh controls if a container or control is modified
  if UIObject is TAHMAbstractControl then
    RefreshCurrentControls;
end;

procedure TAHMScreenLayout.Render;
var
  i: Integer;
  Depth: TAHMDepthType;
begin
  // Okay, we're not busy anymore
  FBusy := False;

  // Delegate rendering to contents - render in order defined by theme
  for Depth := dtBackground to dtForeground do
    for i := 0 to Pred(FContents.Count) do
      if TAHMAbstractControl(FContents.Objects[i]).Depth = Depth then
        TAHMAbstractControl(FContents.Objects[i]).Render(ScreenInstance.Viewport);
end;

procedure TAHMScreenLayout.Assign(Source: TPersistent);
begin
  inherited Assign(Source);

  with Source as TAHMScreenLayout do
  begin
    Self.Contents.Assign(Contents);
    Self.Actions.ClearObjects;
    Self.Actions.BaseList := Actions;
    Self.MetaValues.AssignContents(MetaValues);

    if DefaultControl <> nil then
      Self.SetDefaultControl(DefaultControl.Name);
  end;
end;

procedure TAHMScreenLayout.AssignContents(Source: TAHMUIObject);
begin
  inherited;

  with Source as TAHMScreenLayout do
    Self.Actions.AssignContents(Actions);
end;

procedure TAHMScreenLayout.SaveXMLBody(Lines: TStrings);
var
  oAncs: TAHMScreenLayout;
  i: Integer;
begin
  // Determine which ancestor to compare against - real or empty
  oAncs := TAHMScreenLayout(ObjectToCompare);

  // Do we need to store actions?
  if FActions.IsDifferent(oAncs.Actions) then
  begin
    FActions.XMLIndent := 2;
    FActions.XMLTagName := TAG_ACTIONS;
    FActions.SaveDiffsAsXML(oAncs.Actions, Lines);
  end;

  // Do we need to store contents?
  if FContents.IsDifferent(oAncs.Contents) then
  begin
    Lines.Add(TABS[3] + TGB+TAG_CONTAINS+TGC);
    for i := 0 to Pred(FContents.Count) do
      if not oAncs.Contents.Contains(FContents.Objects[i]) then
        Lines.Add(TABS[4] + FContents.Objects[i].XMLReference);
    Lines.Add(TABS[3] + TGE+TAG_CONTAINS+TGC);
  end;

  // Store default control
  if (FDefaultControl <> nil) and (FDefaultControl <> oAncs.DefaultControl) then
    Lines.Add(TABS[3] + TGB+TAG_DEFAULT_CONTROL+SPC+ATTR_NAME+ATB + EncodeXML(FDefaultControl.Name) + ATE);

  // Do we need to store meta values?
  if FMetaValues.IsDifferent(oAncs.MetaValues) then
  begin
    FMetaValues.XMLIndent := 2;
    FMetaValues.SaveAsXML(Lines);
  end;
end;

procedure TAHMScreenLayout.ParseFromXML(XML: TAHMXMLHelper);
var
  sName: String;
  oContent: TAHMAbstractControl;
  ScreenNode, PropsNode, DetailNode: TAHMXMLNode;
begin
  // Initialise parsing nodes
  ScreenNode := XML.CurrentNode;
  PropsNode := nil;

  // Discover additional properties for this object
  with ApplicationManagerInstance.ThemeManager do
    while XML.NextElement(ScreenNode, PropsNode) do
    begin
      if XML.CurrentElement = TAG_ACTIONS then
      begin
        Actions.ParseFromXML(XML);
      end
      else if XML.CurrentElement = TAG_CONTAINS then
      begin
        DetailNode := nil;

        // Parse list of control and container contents
        while XML.NextElement(PropsNode, DetailNode) do
        begin
          sName := XML.GetAttribute(ATTR_NAME);
          if XML.CurrentElement = TAG_CONTAINER then
            oContent := InterfaceDescriptor.ContainerList.GetContainer(sName)
          else
            oContent := InterfaceDescriptor.ControlList.GetControl(sName);
          if oContent <> nil then
            Contents.Add(oContent)
          else
            LogParsingError(THM_PRS_INT_INVALID_SCREEN_CONTENT + sName, Self);
        end;
      end
      else if XML.CurrentElement = TAG_DEFAULT_CONTROL then
      begin
        // Parse default control to receive focus
        sName := XML.GetAttribute(ATTR_NAME);
        SetDefaultControl(sName);
      end
      else if XML.CurrentElement = TAG_META_INFO then
      begin
        MetaValues.ParseFromXML(XML);
      end;
    end;
end;

procedure TAHMScreenLayout.DeleteUIObject(UIObject: TAHMUIObject);
begin
  inherited;

  // Remove control/container from our list of contents and refresh controls
  if UIObject is TAHMAbstractControl then
  begin
    FContents.RemoveObject(UIObject);
    RefreshCurrentControls;
  end;
end;

procedure TAHMScreenLayout.BuildProperties(Properties: TAHMUIPropertyList);
begin
{$IFDEF DESIGNER}
  inherited;

  // Build custom screen layout properties
  Properties.Add('Actions', 'Actions', CAT_LAYOUT, HINT_ACTIONS);
  Properties.Add('Meta Info', 'MetaValues', CAT_LAYOUT, HINT_METAINFO);
  Properties.Add('Contents', 'Contents', CAT_LAYOUT, HINT_CONTENTS);
  Properties.Add('Default Control', 'DefaultControl', CAT_LAYOUT, HINT_DEFAULT);
{$ENDIF}
end;


end.
